package com.yupi.springbootinit.service.impl;

import co.elastic.clients.elasticsearch.watcher.QueryWatch;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yupi.springbootinit.common.ErrorCode;
import com.yupi.springbootinit.constant.UserConstant;
import com.yupi.springbootinit.exception.BusinessException;
import com.yupi.springbootinit.model.dto.team.TeamJoinRequest;
import com.yupi.springbootinit.model.dto.team.TeamQuery;
import com.yupi.springbootinit.model.dto.team.TeamQuitRequest;
import com.yupi.springbootinit.model.dto.team.TeamUpdateRequest;
import com.yupi.springbootinit.model.entity.Team;
import com.yupi.springbootinit.model.entity.User;
import com.yupi.springbootinit.model.entity.UserTeam;
import com.yupi.springbootinit.model.enums.TeamStatus;
import com.yupi.springbootinit.model.vo.TeamVO;
import com.yupi.springbootinit.model.vo.UserVO;
import com.yupi.springbootinit.service.TeamService;
import com.yupi.springbootinit.mapper.TeamMapper;
import com.yupi.springbootinit.service.UserService;
import com.yupi.springbootinit.service.UserTeamService;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author junjun
 * @description 针对表【team(队伍)】的数据库操作Service实现
 * @createDate 2023-05-26 10:05:51
 */
@Service
public class TeamServiceImpl extends ServiceImpl<TeamMapper, Team>
        implements TeamService {
    @Resource
    private UserTeamService userTeamService;
    @Resource
    private UserService userService;
    @Resource
    private RedissonClient redissonClient;

    /**
     * 创建队伍
     * 1.是否登录
     * 2.参数校验
     * 创建队伍人数在1到20之间，
     * 队伍名称不能为空且不大于20个字符，
     * 描述不能为空且不大于500个字符，
     * 处于加密状态且密码小于20个字符，
     * 处于公开状态且密码为空，
     * 超时时间必须大于当前时间，
     * 该用户创建的队伍数量不能超过5个，
     * 3.插入队伍信息到队伍表
     * 4.插入队伍成员信息到队伍成员关联表
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public long addTeam(Team team, User loginUser) {
        if (team == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);

        }

        if (loginUser == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        int maxNum = Optional.ofNullable(team.getMaxNum()).orElse(0);
        if (maxNum < 1 || maxNum > 20) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍人数在1到20之间");
        }
        String name = team.getName();
        if (StringUtils.isBlank(name) || name.length() > 20) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍名称不能为空且不大于20个字符");
        }
        String description = team.getDescription();
        if (StringUtils.isBlank(description) || description.length() > 500) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "描述不能为空且不大于500个字符");
        }
        int status = Optional.ofNullable(team.getStatus()).orElse(0);
        TeamStatus byCode = TeamStatus.getByCode(status);
        if (byCode == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "状态错误");
        }
        if (byCode == TeamStatus.ENCRYPT) {
            String password = team.getPassword();
            if (StringUtils.isBlank(password) || password.length() > 20) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "处于加密状态,密码不能为空，且密码小于20个字符");
            }
        }
        if (new Date().after(team.getExpireTime())) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "超时时间必须大于当前时间");
        }

        // 多线程并发问题，可能同时出现创建队伍的情况，所以需要加锁
        //todo 分布式锁实现
        String lockKey = UserConstant.CREATE_TEAM_PREFIX + loginUser.getId();
        RLock lock = redissonClient.getLock(lockKey);

        try {
            while(true){
                boolean tryLock = lock.tryLock(0, -1, TimeUnit.SECONDS);
                if(!tryLock){
                    continue;
                }
                QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
                final Long id = loginUser.getId();
                team.setUserId(id);
                queryWrapper.eq("userId", id);
                long hasTeamNum = this.count(queryWrapper);
                if (hasTeamNum >= 5) {
                    throw new BusinessException(ErrorCode.PARAMS_ERROR, "该用户创建的队伍数量不能超过5个");
                }
                team.setId(null);
                boolean save = this.save(team);

                if (!save) {
                    throw new BusinessException(ErrorCode.OPERATION_ERROR, "创建队伍失败");
                }
                UserTeam userTeam = new UserTeam();
                userTeam.setUserId(id);
                userTeam.setTeamId(getId(team));
                userTeam.setJoinTime(new Date());
                boolean save1 = userTeamService.save(userTeam);
                if (!save1) {
                    throw new BusinessException(ErrorCode.OPERATION_ERROR, "创建队伍失败");
                }
                return getId(team);
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

    /**
     * 根据查询条件查询队伍列表
     *
     * @param teamQuery
     * @param request
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<TeamVO> listTeams(TeamQuery teamQuery, HttpServletRequest request) {
        QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
        if (teamQuery == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        boolean isAdmin = userService.isAdmin(request);
        Long id = teamQuery.getId();
        if (id != null && id > 0) {
            queryWrapper.eq("id", id);
        }
        String teamQueryName = teamQuery.getName();
        if (StringUtils.isNotBlank(teamQueryName)) {
            queryWrapper.like("name", teamQueryName);
        }
        String description = teamQuery.getDescription();
        if (StringUtils.isNotBlank(description)) {
            queryWrapper.like("description", description);
        }
        Integer maxNum = teamQuery.getMaxNum();
        if (maxNum != null && maxNum > 0) {
            queryWrapper.eq("maxNum", maxNum);
        }
        Integer status = teamQuery.getStatus();
        if (status != null && status > -1) {
            TeamStatus byCode = TeamStatus.getByCode(status);
            if (byCode == null) {
                byCode = TeamStatus.PUBLIC;
            }
            if (!isAdmin && byCode == TeamStatus.ENCRYPT) {
                throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "没有权限查看加密队伍");
            }
            queryWrapper.eq("status", byCode.getCode());
        }
        Long userId = teamQuery.getUserId();
        if (userId != null && userId > 0) {
            queryWrapper.eq("userId", userId);
        }
        Date expireTime = teamQuery.getExpireTime();
        queryWrapper.and(queryWrapper1 -> queryWrapper1.gt("expireTime", new Date()).or().isNull("expireTime"));

        String searchText = teamQuery.getSearchText();
        if (StringUtils.isNotBlank(searchText)) {
            queryWrapper.and(qw -> qw.like("name", searchText).or().like("description", searchText));
        }


        List<Team> list = this.list(queryWrapper);

        if (CollectionUtils.isEmpty(list)) {
            return new ArrayList<>();
        }

        List<TeamVO> teamVOList = this.getTeamVOList(list);

        return teamVOList;
    }

    /**
     * 更新队伍信息
     * 1.队伍是否存在
     * 2.是否有权限修改
     * <p>
     * 3.修改队伍信息（更新的数据如果和老数据相同，则不更新）
     */

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean updateTeam(TeamUpdateRequest teamUpdateRequest, HttpServletRequest request) {
        if (teamUpdateRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Long id = teamUpdateRequest.getId();
        if (id == null || id < 1) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Team team = this.getById(id);
        if (team == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍不存在");
        }
        User loginUser = userService.getLoginUser(request);
        if (loginUser == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        if (!loginUser.getId().equals(team.getUserId()) && !userService.isAdmin(request)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }
        TeamStatus byCode = TeamStatus.getByCode(teamUpdateRequest.getStatus());
        if (byCode == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "状态错误");
        }
        if (byCode == TeamStatus.ENCRYPT) {
            String password = teamUpdateRequest.getPassword();
            if (StringUtils.isBlank(password) || password.length() > 20) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "处于加密状态,密码不能为空，且密码小于20个字符");
            }
        }
        BeanUtils.copyProperties(teamUpdateRequest, team);
        boolean b = this.updateById(team);
        return b;


    }

    /**
     * 加入队伍，
     * 1.如果队伍是加密状态，需要验证密码
     * 2.如果队伍是私有状态，需要验证是否有权限加入
     * 3.如果队伍是公开状态，直接加入
     * 需要解决并发问题，保证幂等性
     *
     * @param teamJoinRequest
     * @param request
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean joinTeam(TeamJoinRequest teamJoinRequest, HttpServletRequest request) {

        if (teamJoinRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Long teamId = teamJoinRequest.getTeamId();
        if (teamId == null || teamId < 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Team team = this.getById(teamId);
        if (team == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍不存在");
        }
        User loginUser = userService.getLoginUser(request);
        //防止一个用户在一段时间内多次提交，采用分布式锁
        String lockKey = UserConstant.JOIN_TEAM_PREFIX + loginUser.getId();
        RLock lock = redissonClient.getLock(lockKey);

        //自旋锁，没获取到则继续while循环获取，锁的是用户id
        try {
            while (true){
                if(lock.tryLock(0,-1,TimeUnit.MILLISECONDS)){
                    if (loginUser == null) {
                        throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
                    }

                    if (team.getUserId().equals(loginUser.getId())) {
                        throw new BusinessException(ErrorCode.PARAMS_ERROR, "不能加入自己创建的队伍");
                    }
                    QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>();
                    queryWrapper.eq("userId", loginUser.getId());
                    long count = userTeamService.count(queryWrapper);
                    if (count >= 5) {
                        throw new BusinessException(ErrorCode.PARAMS_ERROR, "最多加入5个队伍");
                    }
                    //查询队伍已加入的人数
                    QueryWrapper<UserTeam> queryWrapper1 = new QueryWrapper<>();
                    queryWrapper1.eq("teamId", teamId);
                    long count1 = userTeamService.count(queryWrapper1);
                    if (count1 >= team.getMaxNum()) {
                        throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍人数已满");
                    }
                    //查询是否过期
                    if (team.getExpireTime() != null && team.getExpireTime().before(new Date())) {
                        throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍已过期");
                    }
                    //查询是否加密
                    if (team.getStatus() == TeamStatus.ENCRYPT.getCode()) {
                        String password = teamJoinRequest.getPassword();
                        if (StringUtils.isBlank(password)) {
                            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍已加密，请输入密码");
                        }
                        if (!password.equals(team.getPassword())) {
                            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误");
                        }
                    } else if (team.getStatus() == TeamStatus.PRIVATE.getCode()) {
                        throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍已私密,不可加入");
                    }
                    //判断是否重复加入
                    QueryWrapper<UserTeam> queryWrapper2 = new QueryWrapper<>();
                    queryWrapper2.eq("userId", loginUser.getId());
                    queryWrapper2.eq("teamId", teamId);
                    UserTeam one = userTeamService.getOne(queryWrapper2);
                    if (one != null) {
                        throw new BusinessException(ErrorCode.PARAMS_ERROR, "已加入该队伍");
                    }
                    UserTeam userTeam = new UserTeam();
                    userTeam.setTeamId(teamId);
                    userTeam.setUserId(loginUser.getId());
                    userTeam.setJoinTime(new Date());
                    return userTeamService.save(userTeam);
                }
            }

        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }

    }

    /**
     * 退出队伍
     * 1.只剩1个人时，队伍解散
     * 2.队长退出时，转移给队伍中最早加入的人
     * 3.操作用户队伍关联表
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean quitTeam(TeamQuitRequest teamQuitRequest, HttpServletRequest httpServletRequest) {
        if (teamQuitRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Long teamId = teamQuitRequest.getTeamId();
        if (teamId == null || teamId < 1) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        //查询队伍是否存在
        Team team = this.getById(teamId);
        if (team == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍不存在");
        }
        User loginUser = userService.getLoginUser(httpServletRequest);
        if (loginUser == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        Long userId = loginUser.getId();
        UserTeam userTeam = new UserTeam();
        userTeam.setTeamId(teamId);
        userTeam.setUserId(userId);
        QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>(userTeam);

        long count = userTeamService.count(queryWrapper);
        if (count == 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "未加入该队伍");
        }

        //查询队伍已加入的人数
        QueryWrapper<UserTeam> queryWrapper1 = new QueryWrapper<>();
        queryWrapper1.eq("teamId", teamId);
        long count1 = userTeamService.count(queryWrapper1);
        if (count1 == 1) {
            //队伍只剩一个人，解散队伍
            userTeamService.remove(queryWrapper1);
            return this.removeById(teamId);
        } else {
            //队伍不止一个人
            //查询队长
            if (team.getUserId().equals(userId)) {
                //队长退出
                QueryWrapper<UserTeam> queryWrapper2 = new QueryWrapper<>();
                queryWrapper2.eq("teamId", teamId);
                queryWrapper2.last("order by joinTime asc limit 2");
                List<UserTeam> list = userTeamService.list(queryWrapper2);
                if (CollectionUtils.isEmpty(list)) {
                    throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍异常");
                }
                UserTeam userTeam1 = list.get(1);
                Long nextLeaderId = userTeam1.getUserId();
                team.setUserId(nextLeaderId);
                this.updateById(team);
                boolean remove = userTeamService.remove(queryWrapper);
                if (!remove) {
                    throw new BusinessException(ErrorCode.PARAMS_ERROR, "退出失败");
                }
                return true;
            } else {
                //队员退出
                boolean remove = userTeamService.remove(queryWrapper);
                if (!remove) {
                    throw new BusinessException(ErrorCode.PARAMS_ERROR, "退出失败");
                }
                return true;
            }


        }

    }

    /**
     * 解散队伍
     * 1.队长解散队伍，需要校验队长身份，只有队长才能解散队伍
     * 2.操作用户队伍关联表
     * 3.操作队伍表
     *
     * @param teamQuitRequest
     * @param request
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean dismissTeam(TeamQuitRequest teamQuitRequest, HttpServletRequest request) {
        //校验参数
        if (teamQuitRequest == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Long teamId = teamQuitRequest.getTeamId();
        if (teamId == null || teamId < 1) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        //查询队伍是否存在
        Team team = this.getById(teamId);
        if (team == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "队伍不存在");
        }
        User loginUser = userService.getLoginUser(request);
        if (loginUser == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        Long userId = loginUser.getId();
        if (!team.getUserId().equals(userId)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "只有队长才能解散队伍");
        }
        //删除队伍
        boolean remove = this.removeById(teamId);
        if (!remove) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "解散队伍失败");
        }
        //删除队伍关联表
        QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("teamId", teamId);
        boolean remove1 = userTeamService.remove(queryWrapper);
        if (!remove1) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "解散队伍失败");
        }
        return true;


    }

    /**
     * 获取当前用户已经加入的队伍列表
     *
     * @param request
     * @return
     */
    @Override
    public List<TeamVO> listTeamJoined(HttpServletRequest request) {
        User loginUser = userService.getLoginUser(request);
        if (loginUser == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        Long userId = loginUser.getId();
        QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userId", userId);
        List<UserTeam> list = userTeamService.list(queryWrapper);
        if (CollectionUtils.isEmpty(list)) {
            return Collections.emptyList();
        }
        List<Long> teamIds = list.stream().map(userTeam -> userTeam.getTeamId()).collect(Collectors.toList());
        List<Team> teams = this.listByIds(teamIds);
        if (CollectionUtils.isEmpty(teams)) {
            return Collections.emptyList();
        }
        List<TeamVO> teamVOS = getTeamVOList(teams);
        return teamVOS;
    }

    /**
     * 获取当前用户创建的队伍列表(其实该用户为队长的队伍)
     *
     * @param request
     * @return
     */
    @Override
    public List<TeamVO> listTeamCreated(HttpServletRequest request) {
        User loginUser = userService.getLoginUser(request);
        if (loginUser == null) {
            throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
        }
        Long userId = loginUser.getId();
        QueryWrapper<Team> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userId", userId);
        List<Team> list = this.list(queryWrapper);
        if (CollectionUtils.isEmpty(list)) {
            return Collections.emptyList();
        }
        List<TeamVO> teamVOS = getTeamVOList(list);
        return teamVOS;

    }

    @NotNull
    private List<TeamVO> getTeamVOList(List<Team> teams) {
        return teams.stream().map(team -> {
            TeamVO teamVO = new TeamVO();
            BeanUtils.copyProperties(team, teamVO);
            Long userId = team.getUserId();
            User user = userService.getById(userId);
            teamVO.setUserList(Arrays.asList(userService.getUserVO(user)));
            //查询队伍人数
            QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("teamId", team.getId());
            long count = userTeamService.count(queryWrapper);
            teamVO.setJoinNum((int) count);
            return teamVO;
        }).collect(Collectors.toList());
    }

    private Long getId(Team team) {
        return team.getId();


    }
}




